import Foundation
import libdatachannel

public protocol RTCPeerConnectionDelegate: AnyObject {
    func peerConnection(_ peerConnection: RTCPeerConnection, connectionStateChanged connectionState: RTCPeerConnection.ConnectionState)
    func peerConnection(_ peerConnection: RTCPeerConnection, iceGatheringStateChanged iceGatheringState: RTCPeerConnection.IceGatheringState)
    func peerConnection(_ peerConnection: RTCPeerConnection, iceConnectionStateChanged iceConnectionState: RTCPeerConnection.IceConnectionState)
    func peerConnection(_ peerConnection: RTCPeerConnection, signalingStateChanged signalingState: RTCPeerConnection.SignalingState)
    func peerConnection(_ peerConneciton: RTCPeerConnection, didOpen dataChannel: RTCDataChannel)
    func peerConnection(_ peerConnection: RTCPeerConnection, gotIceCandidate candidated: RTCIceCandidate)
}

public final class RTCPeerConnection {
    /// Represents the state of a connection.
    public enum ConnectionState: Sendable {
        /// The connection has been created, but no connection attempt has started yet.
        case new
        /// A connection attempt is currently in progress.
        case connecting
        /// The connection has been successfully established.
        case connected
        /// The connection was previously established but is now temporarily lost.
        case disconnected
        /// The connection has encountered an unrecoverable error.
        case failed
        /// The connection has been closed and will not be used again.
        case closed
    }

    /// Represents the ICE gathering state of an RTCPeerConnection.
    public enum IceGatheringState: Sendable {
        /// ICE gathering has not yet started.
        case new
        /// The agent is currently gathering ICE candidates.
        case inProgress
        /// ICE gathering has finished. No more candidates will be gathered.
        case complete
    }

    /// Represents the state of the ICE connection for an RTCPeerConnection.
    public enum IceConnectionState: Sendable {
        /// The ICE agent is newly created and no checks have started yet.
        case new
        /// The ICE agent is checking candidate pairs to find a workable connection.
        case checking
        /// A usable ICE connection has been established.
        case connected
        /// ICE checks have completed successfully, and the connection is fully stable.
        case completed
        /// The ICE connection has failed and cannot recover.
        case failed
        /// The ICE connection has been lost or interrupted.
        case disconnected
        /// The ICE agent has been closed and will not be used again.
        case closed
    }

    /// Represents the signaling state of an RTCPeerConnection.
    public enum SignalingState: Sendable {
        /// The signaling state is stable; there is no outstanding local or remote offer.
        case stable
        /// A local offer has been created and set as the local description.
        case haveLocalOffer
        /// A remote offer has been received and set as the remote description.
        case haveRemoteOffer
        /// A provisional (pr-answer) has been set as the local description.
        case haveLocalPRAnswer
        /// A provisional (pr-answer) has been set as the remote description.
        case haveRemotePRAnswer
    }

    static let audioMediaDescription = """
m=audio 9 UDP/TLS/RTP/SAVPF 111
a=mid:0
a=recvonly
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1
"""

    static let videoMediaDescription = """
m=video 9 UDP/TLS/RTP/SAVPF 98
a=mid:1
a=recvonly
a=rtpmap:98 H264/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
"""

    static let bufferSize: Int = 1024 * 16

    /// Specifies the delegate of an RTCPeerConnection.
    public weak var delegate: (any RTCPeerConnectionDelegate)?
    /// The current state of connection.
    public private(set) var connectionState: ConnectionState = .new {
        didSet {
            guard connectionState != oldValue else {
                return
            }
            delegate?.peerConnection(self, connectionStateChanged: connectionState)
        }
    }
    /// The current state of ice connection.
    public private(set) var iceConnectionState: IceConnectionState = .new {
        didSet {
            guard iceConnectionState != oldValue else {
                return
            }
            delegate?.peerConnection(self, iceConnectionStateChanged: iceConnectionState)
        }
    }
    /// The current state of ice gathering.
    public private(set) var iceGatheringState: IceGatheringState = .new {
        didSet {
            guard iceGatheringState != oldValue else {
                return
            }
            delegate?.peerConnection(self, iceGatheringStateChanged: iceGatheringState)
        }
    }
    /// The current state of signaling.
    public private(set) var signalingState: SignalingState = .stable {
        didSet {
            guard signalingState != oldValue else {
                return
            }
            delegate?.peerConnection(self, signalingStateChanged: signalingState)
        }
    }
    private let connection: Int32
    private(set) var localDescription: String = ""

    /// Creates a peerConnection instance.
    public init(_ config: (some RTCConfigurationConvertible)? = nil) throws {
        if let config {
            connection = config.createPeerConnection()
        } else {
            connection = RTCConfiguration.empty.createPeerConnection()
        }
        try RTCError.check(connection)
        do {
            try RTCError.check(rtcSetLocalDescriptionCallback(connection) { _, sdp, _, pointer in
                guard let pointer else { return }
                if let sdp {
                    Unmanaged<RTCPeerConnection>.fromOpaque(pointer).takeUnretainedValue().localDescription = String(cString: sdp)
                }
            })
            try RTCError.check(rtcSetLocalCandidateCallback(connection) { _, candidate, mid, pointer in
                guard let pointer else { return }
                Unmanaged<RTCPeerConnection>.fromOpaque(pointer).takeUnretainedValue().didGenerateCandidate(.init(
                    candidate: candidate,
                    mid: mid
                ))
            })
            try RTCError.check(rtcSetStateChangeCallback(connection) { _, state, pointer in
                guard let pointer else { return }
                if let state = ConnectionState(cValue: state) {
                    Unmanaged<RTCPeerConnection>.fromOpaque(pointer).takeUnretainedValue().connectionState = state
                }
            })
            try RTCError.check(rtcSetIceStateChangeCallback(connection) { _, state, pointer in
                guard let pointer else { return }
                if let state = IceConnectionState(cValue: state) {
                    Unmanaged<RTCPeerConnection>.fromOpaque(pointer).takeUnretainedValue().iceConnectionState = state
                }
            })
            try RTCError.check(rtcSetGatheringStateChangeCallback(connection) { _, gatheringState, pointer in
                guard let pointer else { return }
                if let gatheringState = IceGatheringState(cValue: gatheringState) {
                    Unmanaged<RTCPeerConnection>.fromOpaque(pointer).takeUnretainedValue().iceGatheringState = gatheringState
                }
            })
            try RTCError.check(rtcSetSignalingStateChangeCallback(connection) { _, signalingState, pointer in
                guard let pointer else { return }
                if let signalingState = SignalingState(cValue: signalingState) {
                    Unmanaged<RTCPeerConnection>.fromOpaque(pointer).takeUnretainedValue().signalingState = signalingState
                }
            })
            try RTCError.check(rtcSetTrackCallback(connection) { _, track, pointer in
                guard let pointer else { return }
                if let track = try? RTCTrack(id: track) {
                    Unmanaged<RTCPeerConnection>.fromOpaque(pointer).takeUnretainedValue().didOpenTrack(track)
                }
            })
            try RTCError.check(rtcSetDataChannelCallback(connection) { _, dataChannel, pointer in
                guard let pointer else { return }
                if let channel = try? RTCDataChannel(id: dataChannel) {
                    Unmanaged<RTCPeerConnection>.fromOpaque(pointer).takeUnretainedValue().didOpenDataChannel(channel)
                }
            })
            rtcSetUserPointer(connection, Unmanaged.passUnretained(self).toOpaque())
        } catch {
            rtcDeletePeerConnection(connection)
            throw error
        }
    }

    deinit {
        close()
        rtcDeletePeerConnection(connection)
    }

    /// Adds a `MediaStreamTrack` to the peer connection and associates it with the given `MediaStream`.
    ///
    /// - Parameters:
    ///   - track: The media track to add (audio or video).
    ///   - stream: The `MediaStream` that the track belongs to.
    public func addTrack(_ track: some RTCStreamTrack, stream: RTCStream) throws {
        let msid = stream.id
        switch track {
        case let track as AudioStreamTrack:
            let config = RTCTrackConfiguration(mid: "0", streamId: msid, audioCodecSettings: track.settings)
            let id = try config.addTrack(connection, direction: .sendrecv)
            Task {
                await stream.addTrack(try RTCSendableStreamTrack(id, id: track.id))
            }
        case let track as VideoStreamTrack:
            let config = RTCTrackConfiguration(mid: "1", streamId: msid, videoCodecSettings: track.settings)
            let id = try config.addTrack(connection, direction: .sendrecv)
            Task {
                await stream.addTrack(try RTCSendableStreamTrack(id, id: track.id))
            }
        default:
            break
        }
    }

    @discardableResult
    func addTrack(_ kind: RTCStreamKind, stream: RTCStream) throws -> RTCTrack {
        let sdp: String
        switch kind {
        case .audio:
            sdp = Self.audioMediaDescription
        case .video:
            sdp = Self.videoMediaDescription
        }
        let result = try RTCError.check(sdp.withCString { cString in
            rtcAddTrack(connection, cString)
        })
        let track = try RTCTrack(id: result)
        track.delegate = stream
        return track
    }

    public func setRemoteDesciption(_ sdp: String, type: SDPSessionDescriptionType) throws {
        logger.debug(sdp, type.rawValue)
        try RTCError.check([sdp, type.rawValue].withCStrings { cStrings in
            rtcSetRemoteDescription(connection, cStrings[0], cStrings[1])
        })
    }

    public func setLocalDesciption(_ type: SDPSessionDescriptionType) throws {
        logger.debug(type.rawValue)
        try RTCError.check([type.rawValue].withCStrings { cStrings in
            rtcSetLocalDescription(connection, cStrings[0])
        })
    }

    public func createOffer() throws -> String {
        return try CUtil.getString { buffer, size in
            rtcCreateOffer(connection, buffer, size)
        }
    }

    public func createAnswer() throws -> String {
        return try CUtil.getString { buffer, size in
            rtcCreateAnswer(connection, buffer, size)
        }
    }

    public func createDataChannel(_ label: String) throws -> RTCDataChannel {
        let result = try RTCError.check([label].withCStrings { cStrings in
            rtcCreateDataChannel(connection, cStrings[0])
        })
        return try RTCDataChannel(id: result)
    }

    public func close() {
        do {
            try RTCError.check(rtcClosePeerConnection(connection))
        } catch {
            logger.warn(error)
        }
    }

    private func didGenerateCandidate(_ candidated: RTCIceCandidate) {
        delegate?.peerConnection(self, gotIceCandidate: candidated)
    }

    private func didOpenTrack(_ track: RTCTrack) {
        logger.info(track)
    }

    private func didOpenDataChannel(_ dataChannel: RTCDataChannel) {
        delegate?.peerConnection(self, didOpen: dataChannel)
    }
}

extension RTCPeerConnection.ConnectionState {
    init?(cValue: rtcState) {
        switch cValue {
        case RTC_NEW:
            self = .new
        case RTC_CONNECTING:
            self = .connecting
        case RTC_CONNECTED:
            self = .connected
        case RTC_DISCONNECTED:
            self = .disconnected
        case RTC_FAILED:
            self = .failed
        case RTC_CLOSED:
            self = .closed
        default:
            return nil
        }
    }
}

extension RTCPeerConnection.IceGatheringState {
    init?(cValue: rtcGatheringState) {
        switch cValue {
        case RTC_GATHERING_NEW:
            self = .new
        case RTC_GATHERING_INPROGRESS:
            self = .inProgress
        case RTC_GATHERING_COMPLETE:
            self = .complete
        default:
            return nil
        }
    }
}

extension RTCPeerConnection.IceConnectionState {
    init?(cValue: rtcIceState) {
        switch cValue {
        case RTC_ICE_NEW:
            self = .new
        case RTC_ICE_CHECKING:
            self = .checking
        case RTC_ICE_CONNECTED:
            self = .connected
        case RTC_ICE_COMPLETED:
            self = .completed
        case RTC_ICE_FAILED:
            self = .failed
        case RTC_ICE_DISCONNECTED:
            self = .disconnected
        case RTC_ICE_CLOSED:
            self = .closed
        default:
            return nil
        }
    }
}

extension RTCPeerConnection.SignalingState {
    init?(cValue: rtcSignalingState) {
        switch cValue {
        case RTC_SIGNALING_STABLE:
            self = .stable
        case RTC_SIGNALING_HAVE_LOCAL_OFFER:
            self = .haveLocalOffer
        case RTC_SIGNALING_HAVE_REMOTE_OFFER:
            self = .haveRemoteOffer
        case RTC_SIGNALING_HAVE_LOCAL_PRANSWER:
            self = .haveLocalPRAnswer
        case RTC_SIGNALING_HAVE_REMOTE_PRANSWER:
            self = .haveRemotePRAnswer
        default:
            return nil
        }
    }
}
